package edu.napier.soc.xfdm.parser;

import edu.napier.soc.xfdm.model.*;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.*;

class Interpreter implements ParserConstants, ParserTreeConstants {

	private Database database;
	private volatile int executed;
	private QueryResultSet queryResultSet;
	private String[] commands;
	private volatile boolean stopRequest;

	private final Map<String, SetResult> sets =
			new HashMap<String, SetResult>();
	private final Map<String, Object> singletons =
			new HashMap<String, Object>();
	private final Deque<SimpleNode> loopStack = new LinkedList<SimpleNode>();
	private final Map<Node, List<?>> printColumns =
			new LinkedHashMap<Node, List<?>>();
	private final Queue<Object[]> printsRows = new LinkedList<Object[]>();

	public Interpreter(Database d, QueryResultSet q, String[] commands) {
		reInit(d, q, commands);
	}

	public void reInit(Database database, QueryResultSet queryResultSet,
	                   String[] commands) {
		this.database = database;
		this.queryResultSet = queryResultSet;
		this.commands = commands;
	}

	public void stopRequest() {
		stopRequest = true;
	}

	public void start(SimpleNode root) throws InterpreterException {
		executed = 0;
		stopRequest = false;

		for (int i = 0; i < root.jjtGetNumChildren(); i++) {
			if (stopRequest) {
				queryResultSet.addEvent("requested abort");
				return;
			}
			clear();
			command(root.jjtGetChild(i));
			executed++;
		}
	}

	private void command(SimpleNode n) throws InterpreterException {
		SimpleNode child = n.jjtGetChild(0);

		switch (child.id) {
			case JJTDECLARE:
				declare(child);
				break;
			case JJTIMPERATIVE:
				imperative(child);
				break;
			case JJTCONSTRAINT:
				constraint(child);
				break;
			case JJTLOAD:
				load(child);
				break;
			case JJTDROP:
				drop(child);
				break;
		}
	}

	private void drop(SimpleNode n) throws InterpreterException {
		SimpleNode child = n.jjtGetChild(0);

		switch (child.id) {
			case JJTCONSID:
				Constraint c = database.getConstraint(
						child.value.image);
				if (database.removeConstraint(c)) {
					queryResultSet.addEvent(child.value.image + " removed.");
				} else {
					throwException("invalid constraint", child.value);
				}
				break;
			case JJTFUNC:
				FunSpec f = funspec(child);
				EntityType e = getEntityType(f.arg);
				Function func = getFunction(e, f.funcid);
				e.removeFunction(func);
				queryResultSet.addEvent(func.toString() + " removed.");
				break;
		}
	}

	private void load(SimpleNode n) throws InterpreterException {
		Token path = n.jjtGetChild(0).value;
		SimpleNode unknown = n.jjtGetChild(1);
		Token funcid = null;
		Token typeid;
		if (unknown.id == JJTTYPEID) {
			typeid = unknown.value;
		} else {
			FunSpec func = funspec(unknown);
			typeid = func.arg;
			funcid = func.funcid;
		}

		EntityType et = getEntityType(typeid);
		Function f = null;

		if (funcid != null) {
			f = getFunction(et, funcid);
		}
		String modPath = path.image.substring(1, path.image.length() - 1);
		File file = new File(modPath);

		if (!file.exists()) {
			throwException("file does not exist", path);
		}

		BufferedReader in;

		try {
			in = new BufferedReader(new FileReader(file));

			try {
				if (funcid == null) {
					et.parseEntities(in);
				} else {
					et.parseRelationships(in, f);
				}
			} finally {
				in.close();
			}
		} catch (IOException ex) {
			throwException(ex.getMessage(), path);
		} catch (java.text.ParseException ex) {
			throwException(ex.getMessage(), path);
		} catch (ConstraintException ex) {
			throwConstraintException(ex, path);
		}

		queryResultSet.addEvent("entities loaded into " + et.getName());
	}

	private void imperative(SimpleNode n) throws InterpreterException {
		if (stopRequest) {
			return;
		}
		SimpleNode child;

		while (n != null) {
			child = n.jjtGetChild(0);

			switch (child.id) {
				case JJTTHE:
					the(child);
					n = n.jjtGetChild(1);
					break;
				case JJTEACH:
					Token t = each(child);

					if (t == null) {
						n = loopStack.poll();
						break;
					}
					loopStack.push(n);
					n = n.jjtGetChild(1);
					break;
				case JJTNEW_:
					new_(child);
					n = null;
					break;
				case JJTPRINT:
					print(child);
					n = loopStack.poll();
					break;
				case JJTUPDATE:
					update(child);
					n = loopStack.poll();
					break;
			}

			if (n == null) {
				if (printColumns.size() > 0) {
					compilePrint();
				}
			}
		}
	}

	private boolean next(Token t) {
		SetResult esr = sets.get(t.image);

		Object value = esr.next();

		if (value != null) {
			singletons.put(t.image, value);
			return true;
		} else {
			singletons.remove(t.image);
			return false;
		}
	}

	private void compilePrint() {
		Object[][] rows = new Object[printsRows.size()][];

		for (int i = 0; i < rows.length; i++) {
			rows[i] = printsRows.poll();
		}
		List<Object> columns = new ArrayList<Object>();

		for (Map.Entry<Node, List<?>> entry
				: printColumns.entrySet()) {
			columns.addAll(entry.getValue());
		}

		queryResultSet.add(new QueryResult(commands[executed],
				columns, rows));
	}

	private Token each(SimpleNode n) throws InterpreterException {
		Token t = set(n.jjtGetChild(0));
		if (t == null) {
			return null;
		}
		SetResult sr = sets.get(t.image);
		if (!sr.isEntitySet) {
			throwException("for construct must return entity set", sr.var);
		}
		return sr.var;
	}

	private void update(SimpleNode n) throws InterpreterException {
		SimpleNode child = n.jjtGetChild(0);
		switch (child.id) {
			case JJTDELETE:
				delete(child);
				break;
			case JJTUPDATES:
				standardUpdate(child);
				break;
			case JJTINCLUDE:
				include(child);
				break;
			case JJTEXCLUDE:
				exclude(child);
				break;
		}
	}

	private void include(SimpleNode n) throws InterpreterException {
		FunSpec f = funspec(n.jjtGetChild(0));
		Token vblid = f.arg;
		Token func = f.funcid;

		Entity e = getEntitySingleton(vblid);

		Function function = e.getFunction(func.image);
		SimpleNode stuple = n.jjtGetChild(1);
		for (int i = 0; i < stuple.jjtGetNumChildren(); i++) {
			Token var = the(stuple.jjtGetChild(i));
			Entity add = getEntitySingleton(var);

			try {
				if (e.addValue(function, add)) {
					queryResultSet.addEvent("Entity added to " + function
							+ " function in " + e.getEntityType());
				} else {
					queryResultSet.addEvent("Entity not added to " + function
							+ " function in " + e.getEntityType());
				}
			} catch (ConstraintException ex) {
				throwConstraintException(ex, var);
			}
		}
	}

	private void exclude(SimpleNode n) throws InterpreterException {
		FunSpec f = funspec(n.jjtGetChild(0));
		Token vblid = f.arg;
		Token func = f.funcid;

		Entity sr = getEntitySingleton(vblid);

		Function function = sr.getFunction(func.image);
		SimpleNode stuple = n.jjtGetChild(1);
		for (int i = 0; i < stuple.jjtGetNumChildren(); i++) {
			Token var = the(stuple.jjtGetChild(i));
			Entity add = getEntitySingleton(var);
			if (sr.removeValue(function, add)) {
				queryResultSet.addEvent("Entity removed from " + function
						+ " function in " + sr.getEntityType());
			} else {
				queryResultSet.addEvent("Entity not removed from " + function
						+ " function in " + sr.getEntityType());
			}
		}
	}

	private void delete(SimpleNode n) throws InterpreterException {
		Token vblid = the(n.jjtGetChild(0));

		if (vblid == null) {
			throwException("singleton not bound", null);
		}
		Entity e = getEntitySingleton(vblid);
		database.removeEntity(e);

		queryResultSet.addEvent("entity from " + e.getEntityType().getName()
				+ " removed");
	}

	private Object aggPrint(SimpleNode n) throws InterpreterException {
		List<Object> columns = new ArrayList<Object>();

		if (!printColumns.containsKey(n)) {
			columns.add(ParserConstants.tokenImage[n.value.kind]);
			printColumns.put(n, columns);
		}

		return aggCall(n);
	}

	@SuppressWarnings("unchecked")
	private List<Object> varPrint(SimpleNode n)
			throws InterpreterException {
		Token vblid = n.value;
		EntitySetResult sr = getEntitySet(vblid);
		List<Function> funcs =
				new LinkedList<Function>(sr.entityType.getFunctions());

		if (!printColumns.containsKey(n)) {
			Iterator<Function> iter = funcs.iterator();

			while (iter.hasNext()) {
				Function f = iter.next();

				if (f.isEntityReturn()) {
					iter.remove();
				}
			}
			printColumns.put(n, funcs);
		}

		Entity e = getEntitySingleton(vblid);
		if (e == null) {
			return Arrays.asList(new Object[funcs.size()]);
		}
		funcs = (List<Function>) printColumns.get(n);
		Object[] record = new Object[funcs.size()];
		for (Function f : funcs) {
			if (!f.isEntityReturn()) {
				int index = funcs.indexOf(f);
				record[index] = e.getValue(f);
			}
		}

		return Arrays.asList(record);
	}

	private Entity getEntitySingleton(Token t) throws InterpreterException {
		Object value = singletons.get(t.image);

		if (value == null) {
			return null;
		}
		if (!(value instanceof Entity)) {
			throwException("variable " + t
					+ " is bound to a value when entity expected", t);
		}
		return (Entity) value;
	}

	private Object getSingleton(Token t) {
		return singletons.get(t.image);
	}

	private Object normalPrint(SimpleNode n) throws InterpreterException {
		SetResult sr = svfuncall(n);

		if (sr.isEntitySet) {
			throwException("value set expected", n.jjtGetChild(0).value);
		}

		if (!printColumns.containsKey(n)) {
			Function f = ((ValueSetResult) sr).f;
			List<Function> func = new LinkedList<Function>();
			func.add(f);
			printColumns.put(n, func);
		}

		return sr.next();
	}

	private void print(SimpleNode n) throws InterpreterException {
		List<Object> row = new LinkedList<Object>();
		for (int i = 0; i < n.jjtGetNumChildren(); i++) {
			switch (n.jjtGetChild(i).id) {
				case JJTAGGCALL:
					row.add(aggPrint(n.jjtGetChild(i)));
					break;
				case JJTVBLID:
					row.addAll(varPrint(n.jjtGetChild(i)));
					break;
				case JJTSVFUNCALL:
					row.add(normalPrint(n.jjtGetChild(i)));
					break;
			}
		}
		printsRows.add(row.toArray());
	}

	private Number aggCall(SimpleNode n) throws InterpreterException {
		Token type = n.value;
		Queue<Object> values = new LinkedList<Object>();
		Token t;

		while ((t = set(n.jjtGetChild(0))) != null) {
			values.add(getSingleton(t));
		}

		if (type.kind == COUNT) {
			return values.size();
		}

		if (values.size() < 1) {
			return 0;
		}

		if (!(values.peek() instanceof Number)) {
			throwException("return type not a number", type);
		}

		switch (type.kind) {
			case MIN:
				Number min = (Number) values.poll();
				Number current;
				while ((current = (Number) values.poll()) != null) {
					if (current.doubleValue() < min.doubleValue()) {
						min = current;
					}
				}
				return min;
			case MAX:
				Number max = (Number) values.poll();
				while ((current = (Number) values.poll()) != null) {
					if (current.doubleValue() > max.doubleValue()) {
						max = current;
					}
				}
				return max;
			case SUM:
				double value = 0;

				while ((current = (Number) values.poll()) != null) {
					value += current.doubleValue();
				}
				return value;
			case AVERAGE:
				double val = 0;
				int count = 0;
				while ((current = (Number) values.poll()) != null) {
					val += current.doubleValue();
					count++;
				}
				if (count == 0) {
					throwException("trying to divide by 0", type);
				}
				return val / count;

		}
		return 0;
	}

	private Token the(SimpleNode n) throws InterpreterException {
		Token t = set(n.jjtGetChild(0));

		if (t == null) {
			// inconsistency, going inside the set to get the variable name
			// if t is null since set returns null if no entity / value found.
			t = n.jjtGetChild(0).jjtGetChild(0).value;
			throwException("returned 0 entities, expected singleton", t);
		}
		SetResult sr = sets.get(t.image);
		if (!sr.isEntitySet) {
			throwException("\"the\" construct must return entity set",
					sr.var);
		}
		if (sr.size() != 0) {
			throwException("returned " + (sr.size() + 1) + " entities, " +
					"expected singleton.", sr.var);
		}

		return sr.var;
	}

	private void new_(SimpleNode n) throws InterpreterException {
		Token vblid = n.jjtGetChild(0).value;
		Token typeid = n.jjtGetChild(1).value;
		EntityType e = getEntityType(typeid);
		Entity e1 = new Entity(e);

		try {
			if (n.jjtGetNumChildren() > 2) {
				singletons.put(vblid.image, e1);
				standardUpdate(n.jjtGetChild(2));
				singletons.remove(vblid.image);
			}

			e.addEntity(e1);
		} catch (ConstraintException ex) {
			throwConstraintException(ex, typeid);
		}
	}

	private void standardUpdate(SimpleNode n) throws InterpreterException {
		List<Update> updates = new ArrayList<Update>();

		for (int i = 0; i < n.jjtGetNumChildren(); i++) {
			Update u = makeUpdate(n.jjtGetChild(i));
			updates.add(u);
		}

		for (Update u : updates) {
			Entity entity = getEntitySingleton(u.func.arg);

			try {
				entity.setValue(getFunction(entity, u.func.funcid), u.value);
			} catch (ConstraintException ex) {
				throwConstraintException(ex, u.func.funcid);
			}
		}

		queryResultSet.addEvent("entity(s) added / updated");
	}

	private Update makeUpdate(SimpleNode n) throws InterpreterException {
		Token funcid = n.jjtGetChild(0).value;
		Token arg = n.jjtGetChild(1).value;
		FunSpec function = new FunSpec(funcid, arg);
		List<Object> values = new ArrayList<Object>();

		Entity e = getEntitySingleton(arg);

		if (e == null) {
			throwException("variable " + arg + " not bound to a singleton",
					arg);
		}
		Function f = getFunction(e, funcid);
		boolean isMulti = f.isMultiValued();

		SimpleNode unknown = n.jjtGetChild(2);

		switch (unknown.id) {
			case JJTCONSTANT:
				Object value = convertObject(unknown.jjtGetChild(0).value);
				values.add(value);
				break;
			case JJTSTUPLE:
				for (int i = 0; i < unknown.jjtGetNumChildren(); i++) {
					Token vblid = the(unknown.jjtGetChild(i));
					values.add(getSingleton(vblid));
				}
				break;
		}

		if (!isMulti && values.size() > 1) {
			throwException("singleton expected", funcid);
		} else if (!isMulti) {
			Object value = values.get(0);

			if (!f.validateValue(value)) {
				throwException("invalid data type", funcid);
			}
			return new Update(function, values.get(0));
		}

		if (!f.validateValue(values)) {
			throwException("invalid data type in set", funcid);
		}

		return new Update(function, values);
	}

	private EntityType getEntityType(Token n) throws InterpreterException {
		EntityType result = database.getEntityType(n.image);

		if (result == null) {
			throwException("invalid entity", n);
		}

		return result;
	}

	// This is around the point you are going to wish you had chosen a different
	// project.
	private Token set(SimpleNode n) throws InterpreterException {
		Token vblid = n.jjtGetChild(0).value;
		SetResult set = sets.get(vblid.image);

		SimpleNode unknown = n.jjtGetChild(1);
		SetResult entities = null;

		switch (unknown.id) {
			case JJTTYPEID:
				if (set != null) {
					if (next(vblid)) {
						return vblid;
					} else {
						clearVariable(vblid);
						return null;
					}
				}
				EntityType e = getEntityType(unknown.value);
				entities = SetResult.newEntitySet(e, vblid,
						new LinkedList<Entity>(e.getEntities()));
				break;
			case JJTSVFUNCALL:
				if (set != null) {
					if (next(vblid)) {
						return vblid;
					} else {
						clearVariable(vblid);
						if (unknown.jjtGetChild(1).id == JJTVBLID) {
							// annoying inconsistency, if svfuncall variable
							// is a vblid rather than a set then there will be
							// no more entities left in this set, other wise
							// the set has to be incremented.
							return null;
						}
					}
				}
				entities = svfuncall(unknown);

				if (entities == null) {
					clearVariable(vblid);
					return null;
				}
				if (entities instanceof ValueSetResult) {
					ValueSetResult cast = (ValueSetResult) entities;
					entities = new ValueSetResult(
							cast.entityType, vblid, cast.f, cast.values);
				} else {
					EntitySetResult cast = (EntitySetResult) entities;
					entities = new EntitySetResult(
							cast.entityType, vblid, cast.entities);
				}
				break;
		}

		if (n.jjtGetNumChildren() < 3) {
			if (setupVariable(vblid, entities)) {
				return vblid;
			} else {
				clearVariable(vblid);
				return null;
			}
		}

		if (!entities.isEntitySet) {
			throwException("cannot perform conditions on primitives", vblid);
		}

		unknown = n.jjtGetChild(2);
		Queue<Entity> temp = new LinkedList<Entity>();

		switch (unknown.id) {
			case JJTSINGLETON:
				for (Entity e : ((EntitySetResult) entities).entities) {
					singletons.put(vblid.image, e);
					if (singleton(unknown)) {
						temp.add(e);
					}
				}
				break;
		}

		if (setupVariable(vblid, SetResult.newEntitySet(entities.entityType,
				vblid, temp))) {
			return vblid;
		} else {
			return null;
		}
	}

	private boolean setupVariable(Token vblid, SetResult sr) {
		sets.put(vblid.image, sr);
		Object value = sr.next();
		singletons.put(vblid.image, value);
		return value != null;
	}

	private void clearVariable(Token vblid) {
		sets.remove(vblid.image);
		singletons.remove(vblid.image);
	}

	private boolean singleton(SimpleNode n)
			throws InterpreterException {
		boolean overall = false;
		boolean invert = false;
		for (int i = 0; i < n.jjtGetNumChildren(); i++) {
			SimpleNode unknown = n.jjtGetChild(i);

			switch (unknown.id) {
				case JJTBOOLCON:
					overall = boolCon(unknown);
					if (invert) {
						overall = !overall;
						invert = false;
					}
					break;
				case JJTSOME:
					overall = some(unknown);
					if (invert) {
						overall = !overall;
						invert = false;
					}
					break;
				case JJTBOPERATOR:
					switch (unknown.value.kind) {
						case AND:
							if (!overall) {
								return false;
							}
							break;
						case OR:
							if (overall) {
								return true;
							}
							continue;
					}
					break;
				case JJTUNARY:
					invert = true;
					break;
			}
		}
		return overall;
	}

	private boolean some(SimpleNode n) throws InterpreterException {
		SimpleNode quant = n.jjtGetChild(0);
		Token t;
		boolean overall = false;
		int count = 0;

		while ((t = set(n.jjtGetChild(1))) != null) {
			overall = singleton(n.jjtGetChild(2));

			switch (quant.value.kind) {
				case SOME:
					if (overall) {
						clearVariable(t);
						return true;
					}
					break;
				case ALL:
					if (!overall) {
						clearVariable(t);
						return false;
					}
					break;
				case NO:
					if (overall) {
						clearVariable(t);
						return false;
					}
					break;
				default:
					if (overall) {
						count++;
					}
			}
		}

		switch (quant.value.kind) {
			case LEAST:
				Number num = (Number) convertObject(quant.jjtGetChild(0).value);
				return count >= num.intValue();
			case MOST:
				num = (Number) convertObject(quant.jjtGetChild(0).value);
				return count <= num.intValue();
			case EXACTLY:
				num = (Number) convertObject(quant.jjtGetChild(0).value);
				return count == num.intValue();
		}
		return overall;
	}

	@SuppressWarnings("unchecked")
	private boolean boolCon(SimpleNode n) throws InterpreterException {
		Object value = null;
		switch (n.jjtGetChild(0).id) {
			case JJTAGGCALL:
				value = aggCall(n.jjtGetChild(0));
				break;
			case JJTSVFUNCALL:
				SetResult t = svfuncall(n.jjtGetChild(0));
				value = t.next();
				break;
		}

		Token compOp = n.jjtGetChild(1).value;
		Object constant =
				convertConstant(n.jjtGetChild(2));

		if (constant == null && value == null) {
			return true;
		} else if (constant == null || value == null) {
			return false;
		}

		int comp;
		if (value instanceof String && constant instanceof String) {
			String str = (String) value;
			comp = str.compareToIgnoreCase((String) constant);
		} else if (value instanceof Number && constant instanceof Number) {
			Double d1 = ((Number) value).doubleValue();
			Double d2 = ((Number) constant).doubleValue();

			comp = d1.compareTo(d2);
		} else if (value.getClass().equals(constant.getClass())) {
			if (value instanceof Comparable) {
				comp = ((Comparable) value).compareTo(constant);
			} else {
				return false;
			}
		} else {
			return false;
		}

		switch (compOp.kind) {
			case EQUALS:
				return (comp == 0);
			case MORETHAN:
				return (comp > 0);
			case LESSTHAN:
				return (comp < 0);
			case MORETHANEQUAL:
				return (comp > 0 || comp == 0);
			case LESSTHANEQUAL:
				return (comp < 0 || comp == 0);
			case NOTEQUAL:
				return (comp != 0);
		}
		return true;
	}

	private SetResult svfuncall(SimpleNode n) throws InterpreterException {
		Token funcid = n.jjtGetChild(0).value;
		SimpleNode unknown = n.jjtGetChild(1);
		Token vblid;
		if (unknown.id == JJTSET) {
			vblid = set(n.jjtGetChild(1));

			if (vblid == null) {
				return null;
			}
		} else {
			vblid = unknown.value;
		}

		Entity e = getEntitySingleton(vblid);

		if (e == null) {
			throwException("variable " + vblid + " is not bound to a singleton",
					vblid);
		}
		Function f = getFunction(e.getEntityType(), funcid);

		Queue<Object> values = new LinkedList<Object>();
		values.addAll(getValues(vblid, f));

		if (f.isEntityReturn()) {
			Queue<Entity> entity = new LinkedList<Entity>();

			for (Object o : values) {
				entity.add((Entity) o);
			}
			EntityType et = ((EntityFunction) f).getEntityType();
			return SetResult.newEntitySet(et, vblid, entity);
		} else {
			return SetResult.newValueSet(e.getEntityType(), vblid, f, values);
		}
	}

	private void declare(SimpleNode n) throws InterpreterException {
		FunSpec function = funspec(n.jjtGetChild(0));
		Token base = n.jjtGetChild(1).value;
		Token typeid = n.jjtGetChild(2).value;

		if (function.arg != null) {
			newSingleAttribute(function, base, typeid);
			queryResultSet.addEvent("function " + function.funcid.image
					+ " created");
			return;
		}

		if (base.kind != MULTI) {
			throwException("multivalue base (->>) expected", typeid);
		}

		if ("entity".equalsIgnoreCase(typeid.image)) {
			newSingleEntity(function.funcid);
			queryResultSet.addEvent("entity type " + function.funcid.image
					+ " created");
		} else {
			newExtendEntity(function.funcid, typeid);
			queryResultSet.addEvent("child entity type "
					+ function.funcid.image + " created");
		}
	}

	private void constraint(SimpleNode n) throws InterpreterException {
		Token consid = n.jjtGetChild(0).value;
		List<FunSpec> funlist = funlist(n.jjtGetChild(1));
		Token constype = n.jjtGetChild(2).value;

		try {
			newConstraint(consid, funlist, constype);
		} catch (ConstraintException ex) {
			throwConstraintException(ex, consid);
		}
	}

	private List<FunSpec> funlist(SimpleNode n) {
		List<FunSpec> result = new ArrayList<FunSpec>();

		for (int i = 0; i < n.jjtGetNumChildren(); i++) {
			SimpleNode func = n.jjtGetChild(i);
			Token funcid = func.jjtGetChild(0).value;
			Token arg = func.jjtGetChild(1).value;
			result.add(new FunSpec(funcid, arg));
		}
		return result;
	}

	private FunSpec funspec(SimpleNode n) {
		Token funcid = n.jjtGetChild(0).value;
		Token arg = null;
		if (n.jjtGetNumChildren() > 1) {
			arg = n.jjtGetChild(1).value;
		}

		return new FunSpec(funcid, arg);
	}

	/* data model interface methods. */
	private void newExtendEntity(Token funcid, Token typeid)
			throws InterpreterException {
		EntityType temp;
		EntityType inherit = getEntityType(typeid);

		temp = new EntityType(funcid.image, inherit);

		if (!database.addEntityType(temp)) {
			throwException("entity already exists", funcid);
		}
	}

	private void newSingleEntity(Token funcid) throws InterpreterException {
		EntityType e = new EntityType(funcid.image);

		if (!database.addEntityType(e)) {
			throwException("entity already exists", funcid);
		}
	}

	private void newSingleAttribute(FunSpec func, Token base, Token typeid)
			throws InterpreterException {
		EntityType temp = getEntityType(func.arg);
		boolean multi = (base.kind == MULTI);
		EntityType att = null;
		ReturnType type = getReturnType(typeid);

		if (type == ReturnType.ENTITY) {
			att = getEntityType(typeid);
		}

		Function a = null;

		if (type != ReturnType.ENTITY) {
			a = Function.newSinglePrimitive(func.funcid.image, type);
		} else if (att != null) {
			if (multi) {
				a = Function.newMultiEntity(func.funcid.image, att);
			} else {
				a = Function.newSingleEntity(func.funcid.image, att);
			}
		} else {
			throwException("invalid type", typeid);
		}

		if (!temp.addFunction(a)) {
			throwException("attribute already exists", func.arg);
		}
	}

	private void newConstraint(Token consid, List<FunSpec> funlist,
	                           Token constype)
			throws InterpreterException, ConstraintException {
		if (database.getConstraint(consid.image) != null) {
			throwException("constraint already exists", consid);
		}

		EntityType e = null;

		for (FunSpec f : funlist) {
			if (e == null) {
				e = getEntityType(f.arg);
			} else {
				if (e != getEntityType(f.arg)) {
					throwException("entity name inconsistent", f.arg);
				}
			}
		}

		Set<Function> attributes = new HashSet<Function>();

		for (FunSpec f : funlist) {
			Function a = getFunction(e, f.funcid);
			attributes.add(a);
		}

		ConstraintType ct = null;

		switch (constype.kind) {
			case UNIQUE:
				ct = ConstraintType.UNIQUE;
				break;
			case TOTAL:
				ct = ConstraintType.TOTAL;
				break;
			case FIXED:
				ct = ConstraintType.FIXED;
				break;
		}
		Constraint c = new Constraint(consid.image, ct, e, attributes);
		database.addConstraint(c);
		queryResultSet.addEvent("constraint " + consid.image + " created");
	}

	/* utility methods */
	private void clear() {
		singletons.clear();
		sets.clear();
		printsRows.clear();
		printColumns.clear();
		loopStack.clear();
	}

	private void throwException(String message, Token t)
			throws InterpreterException {
		int line = t.beginLine;
		int column = t.beginColumn;
		String temp =
				String.format("Error: %1$s at line %2$d, column %3$d.",
						message, line, column);
		throw new InterpreterException(
				temp, line, column, executed, commands[executed]);
	}

	private void throwConstraintException(ConstraintException ce, Token t)
			throws IConstraintException {
		int line = t.beginLine;
		int column = t.beginColumn;
		String temp =
				String.format("%1$s at line %2$d, column %3$d.",
						ce.getMessage(), line, column);

		throw new IConstraintException(
				temp, ce.getRow(), line, column, executed, commands[executed]);
	}

	public int getNumberExecuted() {
		return executed;
	}

	private ReturnType getReturnType(Token typeid) {
		String type = typeid.image.toLowerCase();

		if ("string".equalsIgnoreCase(type)) {
			return ReturnType.STRING;
		} else if ("boolean".equalsIgnoreCase(type)) {
			return ReturnType.BOOLEAN;
		} else if ("real".equalsIgnoreCase(type)) {
			return ReturnType.REAL;
		} else if ("integer".equalsIgnoreCase(type)) {
			return ReturnType.INTEGER;
		} else if ("char".equalsIgnoreCase(type)) {
			return ReturnType.CHAR;
		} else {
			return ReturnType.ENTITY;
		}
	}

	private Queue<Object> getValues(Token vblid, Function f)
			throws InterpreterException {
		Entity e = getEntitySingleton(vblid);
		Object o = e.getValue(f);
		Queue<Object> result = new LinkedList<Object>();
		if (o instanceof List) {
			List l = (List) o;

			for (Object o1 : l) {
				if (o != null) {
					result.add(o1);
				}
			}
		} else if (o != null) {
			result.add(o);
		}
		return result;
	}

	@SuppressWarnings("fallthrough")
	private Object convertObject(Token t) throws InterpreterException {
		switch (t.kind) {
			case INTEGER_LITERAL:
				try {
					return Integer.parseInt(t.image);
				} catch (NumberFormatException ex) {
					throwException("invalid data type", t);
				}
			case TRUE:
				return Boolean.TRUE;
			case FALSE:
				return Boolean.FALSE;
			case STRING_LITERAL:
				return t.image.substring(1, t.image.length() - 1);
			case FLOATING_POINT_LITERAL:
				try {
					return Double.parseDouble(t.image);
				} catch (NumberFormatException ex) {
					throwException("invalid data type", t);
				}
			case CHARACTER_LITERAL:
				return t.image.charAt(1);
			case NULL:
				return null;
			default:
				return t.image;
		}
	}

	private Object convertConstant(SimpleNode n) throws InterpreterException {
		Token constant = n.jjtGetChild(0).value;
		return convertObject(constant);
	}

	private Function getFunction(Entity e, Token funcid)
			throws InterpreterException {
		Function f = e.getFunction(funcid.image);

		if (f == null) {
			throwException("function does not exist", funcid);
		}
		return f;
	}

	private Function getFunction(EntityType e, Token funcid)
			throws InterpreterException {
		Function f = e.getFunction(funcid.image);

		if (f == null) {
			throwException("function does not exist", funcid);
		}
		return f;
	}

	private EntitySetResult getEntitySet(Token vblid)
			throws InterpreterException {
		EntitySetResult result = (EntitySetResult) sets.get(vblid.image);

		if (result == null) {
			throwException("variable '" + vblid.image +
					"' not bound to a set", vblid);
		}

		return result;
	}

	/* helper structures */
	private static abstract class SetResult {

		final EntityType entityType;
		final boolean isEntitySet;
		final Token var;

		private SetResult(EntityType entityType, Token var,
		                  boolean isEntitySet) {
			this.entityType = entityType;
			this.isEntitySet = isEntitySet;
			this.var = var;
		}

		abstract public int size();

		abstract public Object next();

		abstract public Object peek();

		//abstract public boolean indexOf(Object value);
		public static EntitySetResult newEntitySet(EntityType entityType,
		                                           Token var,
		                                           Queue<Entity> entities) {
			return new EntitySetResult(entityType, var, entities);
		}

		public static ValueSetResult newValueSet(EntityType entityType,
		                                         Token var,
		                                         Function f,
		                                         Queue<Object> values) {
			return new ValueSetResult(entityType, var, f, values);
		}
	}

	private static class EntitySetResult extends SetResult {

		final Queue<Entity> entities;

		private EntitySetResult(EntityType et, Token var,
		                        Queue<Entity> entities) {
			super(et, var, true);
			this.entities = new LinkedList<Entity>(entities);
		}

		public Entity peek() {
			return entities.peek();
		}

		public Entity next() {
			return entities.poll();
		}

		public int size() {
			return entities.size();
		}

		@Override
		public String toString() {
			String result = "\n";

			for (Entity e : entities) {
				result += e.getValues()[0] + "\n";
			}
			return result;
		}
	}

	private static class ValueSetResult extends SetResult {

		final Function f;
		final Queue<Object> values;

		private ValueSetResult(EntityType et, Token var, Function f,
		                       Queue<Object> values) {
			super(et, var, false);
			this.f = f;
			this.values = values;
		}

		public Object peek() {
			return values.peek();
		}

		public Object next() {
			return values.poll();
		}

		public int size() {
			return values.size();
		}

		@Override
		public String toString() {
			String result = "\n";

			for (Object o : values) {
				result += o + "\n";
			}
			return result;
		}
	}

	private static class Update {

		final FunSpec func;
		final Object value;

		public Update(FunSpec func, Object value) {
			this.func = func;
			this.value = value;
		}

		@Override
		public String toString() {
			return "[" + func + "]" + " = " + value;
		}
	}

	private static class FunSpec {

		final Token funcid;
		final Token arg;

		public FunSpec(Token funcid, Token arg) {
			this.funcid = funcid;
			this.arg = arg;
		}

		public FunSpec(Token funcid) {
			this(funcid, null);
		}

		@Override
		public String toString() {
			return "FuncID: " + funcid.image + ", Arg: " + arg.image;
		}
	}
}